Skip to content

fix(bridge): correct Stellar deposit asset validation#1094

Merged
sameh-farouk merged 1 commit into
developmentfrom
fix/bridge-deposit-asset-validation
Jun 1, 2026
Merged

fix(bridge): correct Stellar deposit asset validation#1094
sameh-farouk merged 1 commit into
developmentfrom
fix/bridge-deposit-asset-validation

Conversation

@sameh-farouk
Copy link
Copy Markdown
Member

Summary

Four correctness fixes to the Stellar→TFChain deposit/mint path (pkg/stellar/stellar.go). These were identified while decomposing the larger batching/idempotency work (#1079) into independently-shippable pieces. They are bridge-only, no runtime upgrade, and independent of the idempotency/batching changes.

Two of the four are fund-safety issues that exist on development today.

Changes

1. Credited-effect asset check: &&|| (fund-safety)

processTransaction skipped an account_credited effect only when both the asset code and the issuer were wrong:

if creditedEffect.Code != asset[0] && creditedEffect.Issuer != asset[1] { continue }

So a credit with the right code but a forged/wrong issuer (or vice-versa) passed the gate. This is the only asset gate before the mint amount is summed. Fixed to require both to match (consistent with the ||-form check already used elsewhere in the file).

2. Per-op loop: return nil, nilcontinue (fund-safety)

The operation loop returned from the entire function on the first non-payment operation, silently discarding any legitimate payment ops later in the same transaction → lost deposits / missing mints. Now skips non-payment ops individually.

3. Operation-level asset validation (defense-in-depth)

The effect check gates entry to the loop, but each payment amount was summed without re-checking its asset. Added a per-payment Code/Issuer guard so a non-TFT payment to the bridge in the same transaction can't be minted as TFT; non-TFT payments are skipped and logged as an alert.

4. StatBridgeAccount: ||&&

Balance lookup matched on code or issuer, so it could return an unrelated asset's balance. Now matches on both. (Observability only — not fund-moving.)

Logic reasoning & regression-vector verification

  • Do legitimate TFT deposits still mint? Yes. A real TFT deposit has the canonical (code, issuer), so it passes both the || effect gate and the new op-level guard unchanged.
  • Can fix dockerize node and add it to graphql setup #1 now wrongly reject good deposits? No — rejection only happens when code or issuer doesn't match the configured TFT asset, which a genuine TFT deposit never does.
  • Can fix document architecture and reason #2 now mint something it shouldn't? No — the op loop still requires op.GetType() == "payment", To == bridge && From != bridge, and (new) the TFT asset match. continue only lets later ops be evaluated under those same guards instead of aborting the tx.
  • Native XLM / other assets: native and non-TFT payments have a non-matching Code/Issuer → skipped+logged by fix investigate test release #3. No mint.
  • Fix Feat/offchain worker #4: native balance (empty code/issuer) and foreign assets no longer match; only the exact TFT trustline balance is returned. Returns the existing "no trustline" error if absent (unchanged behavior).

Testing

  • go build ./... — OK
  • go vet ./pkg/stellar/... — OK
  • gofmt -l — clean

The bridge has no Go unit harness for processTransaction; changes are surgical and reasoned above. (A decode/mint unit test is a worthwhile follow-up.)

🤖 Generated with Claude Code

Three correctness fixes in the Stellar->TFChain deposit path
(processTransaction) and balance reporting (StatBridgeAccount):

1. Credited-effect asset check used && instead of ||, so a credit was
   only skipped when BOTH the asset code and issuer were wrong. A credit
   with the right code but a forged/wrong issuer (or vice-versa) passed
   the gate and could be minted. Now requires both to match.

2. The per-operation loop did `return nil, nil` on the first non-payment
   operation, abandoning the entire transaction and silently dropping any
   legitimate payment operations after it (lost deposits / missing mints).
   Now skips non-payment ops individually with `continue`.

3. Added operation-level asset validation: even after the effect check,
   each payment amount must itself be TFT, otherwise a non-TFT payment to
   the bridge in the same transaction would be summed and minted as TFT.
   Non-TFT payments are now skipped and logged as an alert.

4. StatBridgeAccount matched the TFT balance with || (code OR issuer),
   which could return an unrelated asset's balance; now matches on both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

investigate test release document architecture and reason dockerize node and add it to graphql setup

1 participant